﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Management.Automation;
using System.Text;
using System.Threading;
using System.Xml.Linq;

namespace Community.SharePoint.FAST.PowerShell
{
    /// <summary>
    /// SharePoint PowerShell Community Kit
    /// Set-FASTSearchIndexLocation
    /// This cmdlet configures where Microsoft FAST Search Server 2010 for SharePoint stores it's index files.
    /// Author: Mikael Svenson (mikael.svens
    /// on@comperiosearch.com / miksvenson@gmail.com)
    /// Creation Date: 1/11/2011
    /// http://techmikael.blogspot.com
    /// </summary>
    [Cmdlet(VerbsCommon.Set, "FASTSearchIndexLocation", ConfirmImpact = ConfirmImpact.Medium,
        SupportsShouldProcess = true)]
    public class SetIndexLocation : PSCmdlet
    {
        [Parameter(Position = 0, Mandatory = true)] public string IndexDir;
        [Parameter(Position = 1, Mandatory = true)] public string FixmlDir;
        [Parameter(Position = 2, Mandatory = false)] public SwitchParameter RestartProcesses;
        [Parameter(Position = 3, Mandatory = false)] public SwitchParameter PreserveData;

        private string _configIndexDir;
        private string _configFixmlDir;
        private string _actualIndexDir;
        private string _actualFixmlDir;
        private string _espDir;
        private string _restartProcesses;

        protected override void ProcessRecord()
        {
            if (IsSystemReady() == false) return;
            _espDir = Environment.GetEnvironmentVariable("FASTSEARCH");
            if (string.IsNullOrEmpty(_espDir)) throw new Exception("Cannot locate FASTSEARCH environmental variable");

            ReadRtsplatformrc();
            if (SourceDestinationFoldersAreEqual()) return;

            EnsureDataDirectories();
            MoveIndexData();
            CreateSymlinks();
            if (RestartProcesses)
            {
                StartProcesses();
            }

            GetIndexLocation gil = new GetIndexLocation();
            Dictionary<string, string> folderPaths = gil.ReadFolderPaths();
            WriteObject(folderPaths, true);
        }

        private bool SourceDestinationFoldersAreEqual()
        {
            WriteVerbose("IndexDir: " + IndexDir);
            WriteVerbose("RealIndexDir: " + _actualIndexDir);
            WriteVerbose("FixmlDir: " + FixmlDir);
            WriteVerbose("RealFixmlDir: " + _actualFixmlDir);
            return (IndexDir.ToLower() == _actualIndexDir.ToLower() || FixmlDir.ToLower() == _actualFixmlDir.ToLower());
        }

        private void MoveIndexData()
        {
            if (!PreserveData) return;
            WriteVerbose("Moving data from " + _actualIndexDir + " to " + IndexDir);
            foreach (string directory in Directory.GetDirectories(_actualIndexDir))
            {
                DirectoryInfo di = new DirectoryInfo(directory);
                di.MoveTo(Path.Combine(IndexDir, di.Name));
            }
            foreach (string fileName in Directory.GetFiles(_actualIndexDir))
            {
                FileInfo fi = new FileInfo(fileName);
                fi.MoveTo(Path.Combine(IndexDir, fi.Name));
            }

            WriteVerbose("Moving data from " + _actualFixmlDir + " to " + FixmlDir);
            foreach (string directory in Directory.GetDirectories(_actualFixmlDir))
            {
                DirectoryInfo di = new DirectoryInfo(directory);
                di.MoveTo(Path.Combine(FixmlDir, di.Name));
            }
            foreach (string fileName in Directory.GetFiles(_actualFixmlDir))
            {
                FileInfo fi = new FileInfo(fileName);
                fi.MoveTo(Path.Combine(FixmlDir, fi.Name));
            }

            // Remove old data folders
            Directory.Delete(_actualIndexDir, true);
            Directory.Delete(_actualFixmlDir, true);
        }

        private void CreateSymlinks()
        {
            if (IndexDir.ToLower() != _configIndexDir.ToLower())
            {
                if (Directory.Exists(_configIndexDir))
                {
                    WriteVerbose("Removing existing folder: " + _configIndexDir);
                    Directory.Delete(_configIndexDir, true);
                }
                WriteVerbose("Creating symlink from " + _configIndexDir + " to " + IndexDir);
                DirectoryHelper.CreateSymbolicLink(_configIndexDir, IndexDir);
            }
            if (FixmlDir.ToLower() != _configFixmlDir.ToLower())
            {
                if (Directory.Exists(_configFixmlDir))
                {
                    WriteVerbose("Removing existing folder: " + _configFixmlDir);
                    Directory.Delete(_configFixmlDir, true);
                }
                WriteVerbose("Creating symlink from " + _configFixmlDir + " to " + FixmlDir);
                DirectoryHelper.CreateSymbolicLink(_configFixmlDir, FixmlDir);
            }
        }

        private void ReadRtsplatformrc()
        {
            string rtsplatformrcFilename = Path.Combine(_espDir, @"etc\rtsplatformrc.xml");
            XDocument rtDoc = XDocument.Load(rtsplatformrcFilename, LoadOptions.PreserveWhitespace);

            var folders = from element in rtDoc.Descendants("common")
                          select element;

            XElement dataElement = folders.First();

            _configIndexDir = dataElement.Attribute("indexDir").Value;
            _configFixmlDir = dataElement.Attribute("fixmlDir").Value;

            _actualIndexDir = DirectoryHelper.GetSymbolicLinkTarget(_configIndexDir);
            _actualFixmlDir = DirectoryHelper.GetSymbolicLinkTarget(_configFixmlDir);
        }

        private bool IsSystemReady()
        {
            if (!PreserveData &&
                ShouldContinue("",
                               "Index will be empty upon restart. Use -PreserveData to move the data to the new location.") ==
                false)
            {
                return false;
            }

            List<string> runningProcesses = FASTProcesses.CheckRunningProcesses();
            _restartProcesses = string.Join(" ", runningProcesses.ToArray());
            if (RestartProcesses)
            {
                StopProcesses();
            }

            runningProcesses = FASTProcesses.CheckRunningProcesses();
            if (runningProcesses.Count > 0)
            {
                StringBuilder sb = new StringBuilder();
                sb.Append("You have to stop the following FAST processes before changing the data locations:\n\n\t");
                sb.Append(string.Join("\n\t", runningProcesses.ToArray()));
                sb.Append("\n \n");
                sb.Append("Use the -RestartProcesses parameter, or run the command: nctrl stop ");
                sb.Append(string.Join(" ", runningProcesses.ToArray()));
                sb.Append("\n \n");

                Exception ex = new Exception(sb.ToString());
                ErrorRecord errorRecord = new ErrorRecord(ex, "procrunning", ErrorCategory.InvalidOperation, null);
                WriteError(errorRecord);
                return false;
            }
            return true;
        }

        private void StopProcesses()
        {
            if (string.IsNullOrEmpty(_restartProcesses)) return;
            WriteVerbose("Stopping FAST processes: " + _restartProcesses);
            ProcessStartInfo processStartInfo = new ProcessStartInfo("nctrl.exe", "stop " + _restartProcesses)
                                                    {
                                                        CreateNoWindow = true,
                                                        UseShellExecute = false,
                                                        WorkingDirectory = _espDir
                                                    };
            Process proc = Process.Start(processStartInfo);
            proc.Start();
            proc.WaitForExit();

            int retry = 10;
            while (FASTProcesses.CheckRunningProcesses().Count > 0 && retry <= 10)
            {
                WriteVerbose("Waiting for processes to stop...");
                retry--;
                Thread.Sleep(2000);
            }
            Thread.Sleep(2000);
        }

        private void StartProcesses()
        {
            if (string.IsNullOrEmpty(_restartProcesses)) return;
            WriteVerbose("Starting FAST processes: " + _restartProcesses);
            ProcessStartInfo processStartInfo = new ProcessStartInfo("nctrl.exe", "start " + _restartProcesses)
                                                    {
                                                        CreateNoWindow = true,
                                                        UseShellExecute = false,
                                                        WorkingDirectory = _espDir
                                                    };
            Process proc = Process.Start(processStartInfo);
            proc.Start();
            proc.WaitForExit();
        }

        private void EnsureDataDirectories()
        {
            bool recursiveDelete = !DirectoryHelper.IsDirectorySymlink(IndexDir);
            if (Directory.Exists(IndexDir))
            {
                Directory.Delete(IndexDir, recursiveDelete);
            }
            WriteVerbose("Creating directory " + IndexDir);
            Directory.CreateDirectory(IndexDir);

            recursiveDelete = !DirectoryHelper.IsDirectorySymlink(FixmlDir);
            if (Directory.Exists(FixmlDir))
            {
                Directory.Delete(FixmlDir, recursiveDelete);
            }
            WriteVerbose("Creating directory " + FixmlDir);
            Directory.CreateDirectory(FixmlDir);
        }
    }
}